/*
 * Project: Bearing
 * Copyright (C) 2012 ralfoide gmail com,
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.alfray.bearing.pub.app;

import java.util.ArrayList;
import java.util.List;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.SystemClock;
import android.util.Log;
import android.widget.Toast;

import com.alfray.bearing.R;
import com.alfray.bearing.pub.ui.CompassActivity;


public class GpsService extends Service {

    /*
     * Implementation reference:
     * http://developer.android.com/reference/android/app/Service.html#LocalServiceSample
     */

    public static String TAG = GpsService.class.getSimpleName();
    private static boolean DEBUG = true;

    private static int NOTIF_ID = 'B' << 24 + 'e' << 16 + 'a' << 8 + 'r' << 0;

    private final IBinder mBinder = new GpsBinder();

    private List<GpsServiceListener> mServiceListeners = new ArrayList<GpsService.GpsServiceListener>();

    /**
     * Whether the service is running. Set to true in onCreate, false in onDestroy.
     */
    private static volatile boolean gServiceIsRunning = false;

    /** GPS location listener. */
    private GpsLocationListener mGpsLocListener;
    /** Accuracy in meters */
    private float mLastAccuracy;
    private long mStartTS;

    /** Interface that the service uses to notify binded activities. */
    public interface GpsServiceListener {
        void onGpsLocChanged(Location location);
    }

    /** Interface that callers can use to access the service. */
    public class GpsBinder extends Binder {

        public void addServiceListener(GpsServiceListener listener) {
            synchronized(mServiceListeners) {
                mServiceListeners.add(listener);
            }
        }

        public void removeServiceListener(GpsServiceListener listener) {
            synchronized(mServiceListeners) {
                mServiceListeners.remove(listener);
            }
        }

        public float getLastAccuracy() {
            return mLastAccuracy;
        }

        public long getTimeSinceStart() {
            return SystemClock.uptimeMillis() - mStartTS;
        }

        public void doSomething() {
        }
    }

    /**
     * Whether the service is running. Set to true in onCreate, false in onDestroy.
     */
    public static boolean isServiceIsRunning() {
        return gServiceIsRunning;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        if (DEBUG) Log.d(TAG, "Service onCreate");
        gServiceIsRunning = true;
        showNotification();
        startGps();
        mStartTS = SystemClock.uptimeMillis();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // We want this service to continue running until it is explicitly
        // stopped, so return sticky.
        if (DEBUG) Log.d(TAG, "Service onStartCommand");
        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        if (DEBUG) Log.d(TAG, "Service onBind");
        return mBinder;
    }

    @Override
    public void onDestroy() {
        if (DEBUG) Log.d(TAG, "Service onDestroy");
        gServiceIsRunning = false;
        stopGps();
        removeNotification();
        super.onDestroy();
    }

    // ------

    @Deprecated
    private void notifyGpsStateChanged(boolean state) {
        synchronized(mServiceListeners) {
            for (GpsServiceListener listener : mServiceListeners) {
                //--
            }
        }
    }

    // ------

    private void showNotification() {
        NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

        String text = "Bearing Service Started";

        Notification n = new Notification(
                R.drawable.app_icon, text, System.currentTimeMillis());
        n.flags |= Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR;
        Intent intent = new Intent(this, CompassActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
        PendingIntent pi = PendingIntent.getActivity(
                this, //context
                0,    //requestCode
                intent, //intent
                0 // pending intent flags
                );
        n.setLatestEventInfo(this, text, text, pi);

        nm.notify(NOTIF_ID, n);
    }

    private void removeNotification() {
        NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        nm.cancel(NOTIF_ID);
    }

    private void startGps() {
        if (mGpsLocListener == null) {
            notifyGpsStateChanged(true);

            LocationManager locMan = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

           if (locMan == null) {
               if (DEBUG) Log.d(TAG, "LocationManager service is null");
               return;
           }

           LocationProvider gpsProv = locMan.getProvider(LocationManager.GPS_PROVIDER);
           if (gpsProv == null) {
               if (DEBUG) Log.d(TAG, "No GPS Provider found");
               return;
           }

           mGpsLocListener = new GpsLocationListener();
           locMan.requestLocationUpdates(LocationManager.GPS_PROVIDER,
                            0 /* minTime ms */,
                            0   /* minDistance in meters */,
                            mGpsLocListener);

           Toast.makeText(this, "Bearing: GPS Started", Toast.LENGTH_LONG).show();
        }
    }

    private void stopGps() {

        if (mGpsLocListener != null) {
            notifyGpsStateChanged(false);

            LocationManager locMan = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
            if (locMan != null) {
                locMan.removeUpdates(mGpsLocListener);

                Toast.makeText(this, "Bearing: GPS Stopped", Toast.LENGTH_LONG).show();
            }
            mGpsLocListener = null;
        }
    }


    // ---- GPS and Compass listeners ----

    private class GpsLocationListener implements LocationListener {

        @Override
        public void onProviderDisabled(String provider) {
            // pass
        }

        @Override
        public void onProviderEnabled(String provider) {
            // pass
        }

        @Override
        public void onStatusChanged(String provider, int status, Bundle extras) {
            /*
             * provider  the name of the location provider associated with this update.
status  OUT_OF_SERVICE if the provider is out of service, and this is not expected to change in the near future; TEMPORARILY_UNAVAILABLE if the provider is temporarily unavailable but is expected to be available shortly; and AVAILABLE if the provider is currently available.
extras  an optional Bundle which will contain provider specific status variables.
A number of common key/value pairs for the extras Bundle are listed below. Providers that use any of the keys on this list must provide the corresponding value as described below.

satellites - the number of satellites used to derive the fix

             */
        }

        @Override
        public void onLocationChanged(Location location) {

            if (location.hasAccuracy()) {
                mLastAccuracy = location.getAccuracy();
                // if (DEBUG) Log.d(TAG, "onLocationChanged accuracy = " + Float.toString(mLastAccuracy));
            }

//            if (mIsRecording && mCurrentRecorder != null) {
//                // if (DEBUG) Log.d(TAG, "onLocationChanged record loc = " + location.toString());
//                mCurrentRecorder.record(new TrackLoc(location));
//            }

            synchronized(mServiceListeners) {
                for (GpsServiceListener listener : mServiceListeners) {
                    listener.onGpsLocChanged(location);
                }
            }

        }
    }
}
